package com.tianji.tjpromotion.service.impl;

import cn.hutool.core.bean.copier.CopyOptions;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.tjpromotion.constants.PromotionConstants;
import com.tianji.tjpromotion.domain.dto.UserCouponDTO;
import com.tianji.tjpromotion.domain.po.Coupon;
import com.tianji.tjpromotion.domain.po.ExchangeCode;
import com.tianji.tjpromotion.domain.po.UserCoupon;
import com.tianji.tjpromotion.enums.ExchangeCodeStatus;
import com.tianji.tjpromotion.mapper.CouponMapper;
import com.tianji.tjpromotion.mapper.UserCouponMapper;
import com.tianji.tjpromotion.service.IExchangeCodeService;
import com.tianji.tjpromotion.service.IUserCouponService;
import com.tianji.tjpromotion.utils.CodeUtil;
import com.tianji.tjpromotion.utils.MyLock;
import com.tianji.tjpromotion.utils.RedisLock;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Map;

/**
 * <p>
 * 用户领取优惠券的记录，是真正使用的优惠券信息 服务实现类
 * </p>
 *
 * @author yp
 * @since 2024-08-04
 */
@Service
@RequiredArgsConstructor
public class UserCouponMqServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements IUserCouponService {

    private final CouponMapper couponMapper;
    private final RedisLock redisLock;
    private final IExchangeCodeService exchangeCodeService;

    private final RedissonClient redissonClient;

    private final StringRedisTemplate redisTemplate;

    private final RabbitMqHelper mqHelper;

    @Override
    @MyLock(name = "lock:coupon:#{id}")
    public void receiveCoupon(Long id) {
        // 1.查询优惠券
        Coupon coupon = queryCouponByCache(id);
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }
        // 2.校验发放时间
        LocalDateTime now = LocalDateTime.now();
        if (now.isBefore(coupon.getIssueBeginTime()) || now.isAfter(coupon.getIssueEndTime())) {
            throw new BadRequestException("优惠券发放已经结束或尚未开始");
        }
        // 3.校验库存
        if (coupon.getTotalNum() <= 0) {
            throw new BadRequestException("优惠券库存不足");
        }
        Long userId = UserContext.getUser();
        // 4.校验每人限领数量
        // 4.1.查询领取数量
        String key = PromotionConstants.USER_COUNPON_CACHE_KEY_PREFIX + id;
        Long count = redisTemplate.opsForHash().increment(key, userId.toString(), 1);
        // 4.2.校验限领数量
        if (count > coupon.getUserLimit()) {
            throw new BadRequestException("超出领取数量");
        }
        // 5.扣减优惠券库存
        redisTemplate.opsForHash().increment(
                PromotionConstants.COUPON_CACHE_KEY_PREFIX + id, "totalNum", -1);

        // 6.发送MQ消息
        UserCouponDTO uc = new UserCouponDTO();
        uc.setUserId(userId);
        uc.setCouponId(id);
        mqHelper.send(MqConstants.Exchange.PROMOTION_EXCHANGE, MqConstants.Key.COUPON_RECEIVE, uc);
    }

    private Coupon queryCouponByCache(Long couponId) {
        // 1.准备KEY
        String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId;
        // 2.查询
        Map<Object, Object> objMap = redisTemplate.opsForHash().entries(key);
        if (objMap.isEmpty()) {
            return null;
        }
        // 3.数据反序列化
        return BeanUtils.mapToBean(objMap, Coupon.class, false, CopyOptions.create());
    }


    public Coupon qureyCouponByCache(Long id) {
        String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + id;
        Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
        if (CollUtils.isEmpty(entries)) {
            return null;
        }
        Coupon coupon = BeanUtils.mapToBean(entries, Coupon.class, false, CopyOptions.create());
        if (coupon == null) {
            throw new BizIllegalException("对象不存在");
        }
        return coupon;
    }


    /**
     * 判断是否超出了每人领取的数量和生成用户卷
     *
     * @param coupon
     */
//    @MyLock(name = "coupon:receive:uid:#{userId}",lockType = MyLockType.RE_ENTRANT_LOCK)
    @Transactional
    public void checkAndCreateUserCoupon(Coupon coupon, Long userId) {
        Integer count = lambdaQuery()
                .eq(UserCoupon::getCouponId, coupon.getId())
                .eq(UserCoupon::getUserId, userId)
                .count();
        if (coupon.getUserLimit() <= count) {
            throw new BizIllegalException("领取人数超过一定的数量");
        }
        //5.优惠卷的数量加1
        int rows = couponMapper.incrIssueNum(coupon.getId());
        if (rows == 0) {
            throw new DbException("优惠卷领取失败");
        }

        //6.生成用户卷
        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setCouponId(coupon.getId());
        userCoupon.setUserId(UserContext.getUser());
        //封装优惠卷的使用期限
        LocalDateTime termBeginTime = coupon.getTermBeginTime();
        LocalDateTime termEndTime = coupon.getTermEndTime();
        //如果是固定天数是没有termBeginTime和termEndTime
        if (termBeginTime == null) {
            termBeginTime = LocalDateTime.now();
            termEndTime = termBeginTime.plusDays(coupon.getTermDays());
        }
        userCoupon.setTermBeginTime(termBeginTime);
        userCoupon.setTermEndTime(termEndTime);
        save(userCoupon);
    }

    @Override
    @Transactional
    public void checkAndCreateUserCouponNew(UserCouponDTO msg) {
//        Integer count = lambdaQuery()
//                .eq(UserCoupon::getCouponId, coupon.getId())
//                .eq(UserCoupon::getUserId, userId)
//                .count();
//        if (coupon.getUserLimit() <= count) {
//            throw new BizIllegalException("领取人数超过一定的数量");
//        }
        Long couponId = msg.getCouponId();
        //1.从db中查询Coupon
        Coupon coupon = couponMapper.selectById(couponId);
        if (coupon == null) {
            return;
        }
        //5.优惠卷的数量加1
        int rows = couponMapper.incrIssueNum(coupon.getId());
        if (rows == 0) {
            return;
        }

        //6.生成用户卷
        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setCouponId(coupon.getId());
        userCoupon.setUserId(msg.getUserId());
        //封装优惠卷的使用期限
        LocalDateTime termBeginTime = coupon.getTermBeginTime();
        LocalDateTime termEndTime = coupon.getTermEndTime();
        //如果是固定天数是没有termBeginTime和termEndTime
        if (termBeginTime == null) {
            termBeginTime = LocalDateTime.now();
            termEndTime = termBeginTime.plusDays(coupon.getTermDays());
        }
        userCoupon.setTermBeginTime(termBeginTime);
        userCoupon.setTermEndTime(termEndTime);
        save(userCoupon);
    }


    /**
     * 兑换兑换码 Transactional =》 交给了Spring管理事务 => 通过AOP来实现 => 通过Aop动态代理来调用
     *
     * @param code
     */
    @Override
    @Transactional
    public void exchangeCoupon(String code) {
        //1.解析兑换码
        long id = CodeUtil.parseCode(code);
        //2.查询兑换码
        ExchangeCode exchangeCode = exchangeCodeService.getById(id);
        //3.是否存在
        if (exchangeCode == null) {
            throw new DbException("兑换码不存在");
        }
        //4.是否已经兑换
        if (exchangeCode.getStatus() == ExchangeCodeStatus.USED) {
            throw new BizIllegalException("兑换码不能小重复兑换");
        }

        //5.判断是否过期
        LocalDateTime now = LocalDateTime.now();
        if (now.isAfter(exchangeCode.getExpiredTime())) {
            throw new BizIllegalException("兑换码已经过期");
        }
        //6.是否超出限领的数量
        //7.优惠卷的领取数量加1
        //生成用户劵
        Long userId = UserContext.getUser();
        Coupon coupon = couponMapper.selectById(exchangeCode.getExchangeTargetId());
        checkAndCreateUserCoupon(coupon, userId);
        //9.更新兑换码状态

        exchangeCode.setStatus(ExchangeCodeStatus.USED);
        exchangeCode.setUserId(userId);
        exchangeCodeService.updateById(exchangeCode);
    }
}
